/* Operating System:
 * Assignment 1: Implementation of shell
 * Author : Vaibhav Nipunage
 * BU ID: B00265971
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

extern int errno;

int bg_wait_flag = 0, pg_flag = 0;
pid_t active_pid, ppid;
int p_cnt, j_cnt;
int bg_jobs[30];
char old_path[255];
char old_path1[255];
pid_t my_sh_pgid, my_sh_pid;
pid_t pgid;

typedef struct jobs{
	int pid;
	int pgid;
}jobs;

jobs job_id[20];

int start_shell();

/* type definition of "pointer to a function that takes integer argument and returns void */
typedef void Sigfunc(int);
void put_job_background (int job);
void put_job_foreground(int job, int cond);
char ** parse_line(char *line, char *cmd, char **args, int *n);

int count_args(char *line){

	char *str_ptr = NULL;
	char *tmp_str = malloc (sizeof (char) * strlen (line));

	int n = 1;
	
	strcpy (tmp_str, line);

	str_ptr = strtok(tmp_str, " ");
	
	while ((str_ptr = strtok (NULL, " ")) != NULL)
		n++;
	return n;
}

/* Parse the given input */
char ** parse_line(char *line, char *cmd, char **args, int *n){

	int i;
	char *str_ptr = NULL;

	cmd = NULL;

	*n = count_args(line);

	args = (char **) malloc (sizeof (char *) * (*n + 1));
	args[0] = (char *) malloc (sizeof (char ) * sizeof (cmd));
	
	cmd = strtok (line, " \n");
	if (cmd == NULL){
		return (char **)-1;
	}
	
	strcpy (args[0], cmd);

	i = 1;
	while ((str_ptr = strtok (NULL, " \n")) != NULL){
		args[i] = (char *)malloc (sizeof (char ) * sizeof (str_ptr));
		strcpy (args[i], str_ptr);
		i++;
	}
	args[i] = (char *)0;
	
	return args;
}

/* Create new job array*/
int create_job (int pid){

	job_id[j_cnt].pid = pid;
	job_id[j_cnt].pgid = pid;
	j_cnt ++;
	return j_cnt - 1;
}

/* Remove a job from array after that job 
 * gets killed or exited */
void remove_job(pid_t pid){
	int i;
	int found = 0;

	for (i = 0; i < j_cnt; i++){
		if (job_id[i].pid == pid){
			found = 1;
			j_cnt--;
		}	
		if (found){
			job_id[i] = job_id[i+1];
		}	
	}
}

int get_first_job(){
	return j_cnt - 1;
}

/* Handle various signals here */
void shell_signal_handler(int sig) {

	int stat, job;
	switch(sig) {

		case SIGINT:
			/*Do Nothing. Reset the signal */
			signal(SIGINT,shell_signal_handler);
			printf ("\n\0");
		break;

		case SIGTSTP:
			fflush(stdout);
			bg_jobs[p_cnt++] = active_pid;
			job = create_job (active_pid);			
			printf ("[%d]\t%d \n",job+1, active_pid);	
			put_job_background (job);
		break;

		case SIGCHLD:
			/* Wait for zombie cild */
			while (waitpid (-1, &stat, WNOHANG) > 0);
			return;
		break;	

	}
}

/* Put job in background */
void put_job_background (int job){
	tcsetpgrp (STDIN_FILENO, my_sh_pgid); 
}

/* Bring back background jobs */
void put_job_foreground(int job, int cond){
	
	int status;
	
	tcsetpgrp(STDIN_FILENO, job_id[job].pgid);
	//if (cond)
	kill (-job_id[job].pid, SIGCONT);	
	
	waitpid(job_id[job].pid, &status, WNOHANG);
	tcsetpgrp(STDIN_FILENO, my_sh_pgid);
}

/* This function  installs a signal handler for 'signo' signal */
Sigfunc *signal(int signo, Sigfunc *handler)
{
	struct sigaction act, oact;

	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	if( signo == SIGALRM ) {
#ifdef SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT;  /* SunOS 4.x */
#endif
	} else {
#ifdef SA_RESTART
		act.sa_flags |= SA_RESTART;  /* SVR4, 4.4BSD */
#endif
	}

	if( sigaction(signo, &act, &oact) < 0 )
		return (SIG_ERR);

	return(oact.sa_handler);
}

/* Allocate the memory for the command that we will send to execvp & copy that commmand */
char ** get_next_cmd_set(char **args, char **cmd_set, int i, int pos){
	
	int k = 0, j;

	cmd_set = (char **) malloc (sizeof (char *) * (i - pos + 1));

	for (j = pos; j < i; j++) {
		cmd_set[k] = (char *) malloc (sizeof (char ) * sizeof (args[j]));
		strcpy (cmd_set[k++],args[j]);
	}
	cmd_set[k] = NULL;
	return cmd_set;
}

void free_cmd_set(char **cmd_set){

	int j;

	for (j = 0; cmd_set[j] != NULL; j++)
		free(cmd_set[j]);

	free(cmd_set);
}

/* Create new chid process so that the command will get executed in the child process.
 * Arguments for this functions are command, then if the entered command contain pipe 
 * then we need to create set of fds & also we need to close appropriate fds before 
 * sending the data between multiple commands.
 */
int create_child_process(char **new_args, int sout, int sin, int pout, int pin, int ex_mode){
	
	int i;
	pid_t pid;
	int status;
	int job;
	
	pid = fork();
	if (pid < 0){
		perror("fork error: ");
		return -1;
	}

	if (pid == 0){
		if (sout != -1)
			close (sout);
		if (sin != -1)
			close (sin);
		if (pout != -1){
			dup2 (pout, 1);
			close (pout);
		}	
		if (pin != -1){
			dup2 (pin, 0);
			close(pin);
		}	

		if (ex_mode == 2)
			tcsetpgrp(STDIN_FILENO, getpid());  	/* Foreground process */

		if (ex_mode != 3){
			
			if(execvp (new_args[0], new_args) == -1){
				printf ("%s: command not found\n", new_args[0]);
				exit (EXIT_FAILURE);
			}
		}
		
		exit(0);
	}else {

		if (pg_flag == 0){
			pgid = setpgid(pid, pid);	
			pg_flag = 1;
		}
		else{
			setpgid(pid, pgid);	
		}
		
		if (ex_mode == 1){
			job = create_job(pid);
			put_job_background(job);
		}

		if (ex_mode == 2)
			put_job_foreground(job, 0);

		if (!bg_wait_flag) {

			active_pid = getpid();
			ppid = getppid();
			waitpid (pid, &status, WUNTRACED);
			bg_wait_flag = 0;
			return;
		}
	}
	active_pid = getpid();
	ppid = getppid();
}

/* Check if the given command contaion any combination of '|', '>', '<', '>>', '&' etc */
int check_combination(char **args, int n){

	char **cmd_set;
	int i, pos, k, j, retval = 0;
	char **tmp_arr = NULL;
	int pfds1[2], pfds2[2], fd, in_fd;
	int first_time = 1, flag = 0, pflag = 0;
	int cmd_flg = 0, in_flg = 0;

	pfds2[0] = -1;

	pos = 0;
	for (i = 0; args[i] != NULL; i ++){

		if (strcmp (args[i], "|") == 0){

			if (pflag == 0){
				if (pipe (pfds1) == -1){
					perror("pipe():");
					return -1;
				}
			}

			if (in_flg){
				create_child_process(tmp_arr, 1, 0, pfds1[1], in_fd, 0);	
				pflag = 1;
				first_time = 0;
				in_flg = 0;
			}else{

				if (pflag == 1){
					if (pipe (pfds2) == -1){
						perror("pipe():");
						return -1;
					}
				}
				if (pflag == 0)
					pflag = 1;
				else
					pflag = 0;

				cmd_set = get_next_cmd_set (args, cmd_set, i, pos);
				if (first_time){
					create_child_process (cmd_set, 1, -1, pfds1[1], -1, 0);
					first_time = 0;
				}	
				else{
					close (pfds1[1]);
					create_child_process (cmd_set, 1, 0, pfds2[1], pfds1[0], 0);
				}	

				free_cmd_set(cmd_set);
			}
			pos = i + 1;
			retval = 1;
			cmd_flg = 1;
		} else if ((strcmp (args[i], ">") == 0) || (strcmp (args[i], ">>") == 0) ){
			
			cmd_set = get_next_cmd_set (args, cmd_set, i, pos);
			
			if (strcmp (args[i], ">>") == 0)
				fd = open (args[i+1], O_APPEND | O_WRONLY);
			else{	
				fd = open (args[i+1], O_CREAT | O_WRONLY, 0666);
				ftruncate(fd, 0);
			}
			if (fd == -1){
				perror ("file open error : ");
				return -1;
			}
			if (in_flg){
				create_child_process (tmp_arr, 1, 0, fd, in_fd, 0);
			}else{
				if (first_time == 0){
					if (pfds2[0] == -1){
						close (pfds1[1]);
						create_child_process (cmd_set, 1, 0, fd, pfds1[0], 0);
					}	
					else{	
						close (pfds1[0]);
						close (pfds1[1]);
						close (pfds2[1]);
						create_child_process (cmd_set, 1, 0, fd, pfds2[0], 0);
					}
				}
				else {
					create_child_process (cmd_set, 1, -1, fd, -1, 0);
				}
			}
			
			free_cmd_set(cmd_set);
			
			pos = i + 1;
			flag = 1;
			retval = 1;
			cmd_flg = 1;
			close(fd);
			
		} else if (strcmp (args[i], "<") == 0){
						
			cmd_set = get_next_cmd_set (args, cmd_set, i, pos);
			
			fd = open (args[i+1], O_CREAT | O_RDONLY, 0666);
			if (fd == -1){
				perror ("file open error : ");
				return -1;
			}
		
			if (args[i + 2] != NULL){
				in_flg = 1;
				in_fd = fd;
				tmp_arr = get_next_cmd_set (args, tmp_arr, i, pos);
			}else{

				if (first_time == 0){
					if (pfds2[0] == -1){
						close (pfds1[1]);
						create_child_process (cmd_set, 1, 0, fd, pfds1[0], 0);
					}	
					else{	
						close (pfds1[0]);
						close (pfds1[1]);
						close (pfds2[1]);
						create_child_process (cmd_set, 1, 0, fd, pfds2[0], 0);
					}
				}
				else {
					create_child_process (cmd_set, -1, 0, -1, fd, 0);
				}

				free_cmd_set(cmd_set);
				flag = 1;
			}
			pos = i + 1;
			retval = 1;
			cmd_flg = 1;
			
		}else if (strcmp (args[i], "&") == 0){
			
			bg_wait_flag = 1;	

			cmd_set = get_next_cmd_set (args, cmd_set, i, pos);
			
			create_child_process (cmd_set, -1, -1, -1, -1, 1);
			
			free_cmd_set(cmd_set);
			retval = 1;

		}
	}
	if (args[i] == NULL && cmd_flg){
		k = 0;

		cmd_set = (char **) malloc (sizeof (char *) * (i - pos + 1));
		for (j = pos; j < i; j++){
			cmd_set[k] = (char *) malloc (sizeof (char ) * sizeof (args[j]));
			strcpy (cmd_set[k++],args[j]);
		}       
		cmd_set[k] = NULL;

		cmd_set = get_next_cmd_set (args, cmd_set, i, pos);

		if (flag == 0) /* If last string is cmd */
		{
			if (pfds2[0] == -1){
				close (pfds1[1]);	
				create_child_process (cmd_set, -1, 0, -1, pfds1[0], 0);
			}	
			else{	
				close (pfds1[0]);
				close (pfds1[1]);
				close (pfds2[1]);
				create_child_process (cmd_set, -1, -1, -1, pfds2[0], 0);
			}	
		}
			
		free_cmd_set(cmd_set);
	}
	if (tmp_arr)
		free(tmp_arr);

	return retval;
}

/* Handle special commands here. like 'kill', 'fg', 'cd', 'jobs' */
int special_cmd(char **args, int n){
	int retval = 0;
	char tmp[15];
	int pid, sig;
	int job;
	
	if (strcmp (args[0], "exit") == 0)
		exit(EXIT_SUCCESS);


	if (strcmp (args[0], "kill") == 0){
		if (n != 4 ){
			printf ("Insufficient arguments.\n");
			printf ("Usage: \n\tkill -s <SIGNAL_NO> pid\n");
			return -1;
		}	
		
		pid = atoi(args[3]);
		sig = atoi(args[2]);
		if ((kill (pid,sig)) == -1){
			perror ("kill:");
			return -1;
		}

		kill (ppid, SIGCHLD);
		remove_job(pid);
		return 1;
	}

	if (strcmp (args[0], "jobs") == 0){
		int i = 0;

		for (i = 0; i < j_cnt; i++)
			printf ("[%d]\t%d\n",i+1, job_id[i].pid);
		
		return 1;	
	}
	
	if (strcmp (args[0], "fg") == 0){
		job = get_first_job();
		put_job_foreground(job, 0);
		return 1;
	} 
	if (strcmp (args[0], "cd") == 0){

		chdir (args[1]);
		return 1;
	}

}

int start_shell (){
	
	int error,retval;
	pid_t pid;
	size_t len;
	char *line = NULL;
	char *cmd = NULL;
	char **args = NULL;
	int i = 0, n = 0, status;

	char special[20];
	
	/* Set all appropriate signals */
	signal (SIGINT, shell_signal_handler);
	signal (SIGTSTP, shell_signal_handler);
	signal (SIGCHLD, shell_signal_handler);

	my_sh_pid = getpid();
	while (tcgetpgrp(STDIN_FILENO) != (my_sh_pgid = getpgrp()))
                        kill (my_sh_pid, SIGTTIN);

	setpgid (my_sh_pid, my_sh_pid);                                         // we make the shell process as new process group leader
	my_sh_pgid = getpgrp();
	if (tcsetpgrp (STDIN_FILENO, my_sh_pgid) == -1)
		printf ("Not good\n");

	while (1){
		
		line = NULL;
		fflush(stdout);
		fprintf (stdout, "\ncs552sh>");
	
		/* Read command line input */
		if (getline (&line, &len,stdin) == -1){
			printf ("Unable to read command.\n");
			exit(EXIT_FAILURE);
		}
		
		if (line == NULL){
			continue;
		}
		
		args = parse_line (line, cmd, args, &n);
		if (args == (char **)-1){
			if (line)
				free(line);
			continue;
		}
		
		pg_flag = 0;
		retval = special_cmd (args, n);
		if (retval == 1 || retval == -1)
			continue;

		retval = check_combination (args, n); 

		if (retval == 0){
			/* Create child process */
			pid = fork();
			if (pid < 0){
				perror("fork error: ");
				continue;
			}

			if (pid == 0){
				pid_t child_pid;

				child_pid = getpid();
				if (retval == 0){
					if(execvp (args[0], args) == -1){
						fflush(stdout);
						printf ("%s: command not found\n",args[0]);
						exit (EXIT_FAILURE);
					}
				}
			}
			else{
				active_pid = pid;
				ppid = getppid();
				waitpid (pid, &status, WUNTRACED); 
			}
		}	
		for (i = 0; i < n; i++)
			free (args[i]);

		free (args);

		if (line)
			free (line);
	}
}

int main(){
	/* Start of shell */
	start_shell();	
}
